"""
文件名称 : semantic_preservation.py
作者 : zzl
描述 :
修改历史:
创建于 2024/6/20 下午7:09
"""
from collections import deque

from storage.lru_store.embed_storage import PersistentStorage


class neo4jreporter():
    all_edge = 0
    compacted_edge = 0
    success_edge = 0
    vertex = 0
    success_vertex = 0
    start_time = 12
    end_time = 13

    zzl_compact_edge = 0
    zzl_compact_edge_no_op = 0

    zzl_enforce_compact_infor = set()


class GS_compact:
    def __init__(self, dbname, cache_len):
        self.source=set()
        self.source2exist_d = PersistentStorage(cache_len, dbname)
        # 压缩的次数
        self.compact_num = 0
        self.dbname = dbname
        # 记录所有边的总数以及压缩情况   type: [sum, compact_sum]
        self.compact_status = {}


    def update_compact_status(self,edgetype, success):
        """如果边压缩成功,添加压缩成功的个数，否则，只添加到总数
        """
        if edgetype in self.compact_status:
            self.compact_status[edgetype][0]+=1
            if success:
                self.compact_status[edgetype][1]+=1
        else:
            self.compact_status[edgetype] = [1,0]
            if success:
                self.compact_status[edgetype][1]+=1



    def global_semantic_compact(self,source,destination):
        # update state
        if source in self.source:
            pass
        else:
            # 源节点不在，初始化源节点 语义（信息流）操作 的目的节点
            self.source.add(source)
            self.source2exist_d.update(source,set())

        # 存在相同语义边，删除此事件,可压缩
        if destination in self.source2exist_d.get(source):
            self.compact_num += 1
            return True
        else:
            self.source2exist_d.get(source).add(destination)

            # 更新了目标节点的语义,则目标节点不存在 可删除语义操作边，不可压缩
            self.source.add(destination)
            self.source2exist_d.update(destination,set())
            return False


class CompleteSemantic:
    '''保留所有的源节点依赖的压缩'''


    def __init__(self, dbname, cache_len):

        # 记录节点的所有输入源
        self.node_denpend = PersistentStorage(cache_len, dbname+'_node_dp')

        self.dbname = dbname

        # 记录源节点依赖不变时写入的操作边
        self.keep_semantic_writein = PersistentStorage(cache_len, dbname+'_node_wr')

        # 压缩的次数
        self.compact_num = 0
        # 记录所有边的总数以及压缩情况   type: [sum, compact_sum]
        self.compact_status = {}


    def update_compact_status(self,edgetype, success):
        """如果边压缩成功,添加压缩成功的个数，否则，只添加到总数
        """
        if edgetype in self.compact_status:
            self.compact_status[edgetype][0]+=1
            if success:
                self.compact_status[edgetype][1]+=1
        else:
            self.compact_status[edgetype] = [1,0]
            if success:
                self.compact_status[edgetype][1]+=1



    def complete_semantic_compact(self, source, dest):

        can_be_compacted = False

        # 初始化 source 节点依赖不变的写入集合
        if self.keep_semantic_writein.get(source) == None:
            self.keep_semantic_writein.set(source, set())

        if dest in self.keep_semantic_writein.get(source):
            # 此边可以省略
            can_be_compacted = True
            self.compact_num += 1
            return True
        else:
            # 添加dest，之后依赖不变的写，可以省略
            self.keep_semantic_writein.get(source).add(dest)


        # 初始化 source,dest 的依赖集合
        if self.node_denpend.get(source) == None:
            self.node_denpend.set(source,set())
        if self.node_denpend.get(dest) == None:
            self.node_denpend.set(dest,set())

        # 查看目的节点依赖是否改变

        # 1.获取要累加在dest的 信息源集合
        s_semantic = self.node_denpend.get(source).union({source})

        # 获取dest的原有依赖集合
        d_semantic = self.node_denpend.get(dest)

        if s_semantic == d_semantic:
            # 依赖不变
            pass
        else:
            # 依赖发生改变
            d_semantic.update(s_semantic)
            # 清空dest节点的 keep_semantic_writein集合
            self.keep_semantic_writein.update(dest, set())

        return can_be_compacted


class EnDependencyRemain:
    '''用来增强依赖压缩（保留最新的节点依赖），平衡开销与多层循环的问题'''

    # # 记录依赖的最大长度。
    # DP_LEN = 3
    # # 记录节点唯一标识 uuid 的长度。
    # NODE_LEN = 36


    def __init__(self,nodelen,dplen, dbname, cache_len):
        '''初始化节点状态信息

        参数：
            nodelen: 代表节点的唯一标识大小，因为依赖是标识加版本，所以需要利用此信息。
            dplen: 依赖的个数大小。依赖越多，循环冗余压缩能力越强。依赖个数记录为1 能解决2节点间的压缩。
            dbname: 存储数据库的名称
            cache_len: 存储数据在内存中的长度
        返回值：
            无
        '''
        # 

        # 指定Node的唯一标识长度,默认为64
        self.NODE_LEN = nodelen

        # 指定记录依赖的队列大小，默认为3.      < n个依赖可解决n+1层循环的冗余 >
        self.DP_LEN = dplen

        self.dbname = dbname
        # 保存节点的当前版本
        self.node_v = PersistentStorage(cache_len, dbname+'_node_v')

        # 保存节点的依赖节点信息。依赖的长度为 L
        self.node_dp = PersistentStorage(cache_len, dbname+'_node_dp')

        # 保存节点依赖不变的写入节点
        self.node_wr = PersistentStorage(cache_len, dbname+'_node_wr')

        # 循环的情况
        self.cycle = 0

        # 压缩的次数
        self.compact_num = 0

        # 记录所有边的总数以及压缩情况   type: [sum, compact_sum]
        self.compact_status = {}


    def update_compact_status(self,edgetype, success):
        """如果边压缩成功,添加压缩成功的个数，否则，只添加到总数
        """
        if edgetype in self.compact_status:
            self.compact_status[edgetype][0]+=1
            if success:
                self.compact_status[edgetype][1]+=1
        else:
            self.compact_status[edgetype] = [1,0]
            if success:
                self.compact_status[edgetype][1]+=1





    def enhence_dependency_remain_compact(self, source, dest):
        '''
        查看此source-->dest 边是否可以省略
        原理：
            依赖不变的节点，只需要写入一次dest，后续边可省略而不影响取证。
            1. 保留节点 DP_LEN 个最新依赖。
            2. 将源节点source的依赖传递给 dest.
            3. 如果目的节点dest有新增依赖new_dp，则依赖改变。版本加一
            4. 引起dest依赖改变new_dp中的节点 X，如果同时依赖于dest与new_dp中的节点。 则 X 依赖于最新版本的dest.
        '''

        can_be_compacted = False

        # 初始化源节点信息。
        if self.node_v.get(source)==None:
            # 初始化版本为0
            self.node_v.set(source,0)

            # 初始化依赖为空的双向队列，最大长度为DP_LEN
            self.node_dp.set(source,deque(maxlen=self.DP_LEN))

            # 初始化写入节点为 空集合。
            self.node_wr.set(source,set())

        # 初始化目的节点信息。
        if self.node_v.get(dest) == None:
            # 初始化版本为0
            self.node_v.set(dest,0)

            # 初始化依赖为空的双向队列，最大长度为DP_LEN
            self.node_dp.set(dest, deque(maxlen=self.DP_LEN))

            # 初始化写入节点为 空集合。
            self.node_wr.set(dest, set())

        # 如果source依赖不变的情况下，已经写过dest,可省略
        if dest in self.node_wr.get(source):
            # 此边可以省略
            can_be_compacted = True
            self.compact_num += 1
            return can_be_compacted


        # 源节点的写入集合增加Dest
        self.node_wr.get(source).add(dest)

        # 获取目的节点的新增的依赖信息。
        new_dp = self.merge_dp(source, dest)

        # 如果没有新的依赖，则dest依赖不变
        if new_dp == set():
            return can_be_compacted

        # dest的依赖发生改变, dest的写入集合置空,版本加 1
        self.node_wr.update(dest, set())

        version = self.node_v.get(dest)
        self.node_v.update(dest, version+1)


        # 如果依赖变化，则节点版本需要加一，
        # 且如果new_dp中的节点 X 的依赖集合包含所有的new_dp，
        #   即引起dst节点依赖变化new_dp中的节点 X ，同时也依赖dst的依赖变化前版本 及 new_dp.
        #   则表示，dst依赖的变化，其实并不影响 X的依赖,X同时依赖于最新版本的dst节点
        # --> 更新 X 的依赖为最新版本的 dst

        copy_new_dp = new_dp.copy()
        for element in copy_new_dp:
            # 去除element的版本信息,获取引起 dst节点依赖变化的节点 X
            X = element[:self.NODE_LEN]
            self.remain_dp(X, new_dp, dest+str(self.node_v.get(dest)-1))

        return can_be_compacted

    def remain_dp(self,X, new_dp, dest):
        ''' 更新 X节点 依赖为节点的最新版本
        原理：
            主要判断X是否保持对dest的依赖。
                即判断引起dst节点依赖变化的节点 X，是否依赖dst的依赖变化前版本 及 new_dp。
            if True：
                则表示引起dst依赖变化的信息源， 已经在 X 的依赖里面了， 则 X 仍然依赖于 dst.
            if False：
                则dst的依赖变化，X不再依赖于最新的dst
        参数：
            X:      引起dst节点依赖变化的节点 X
            new_dp: 引起dst节点依赖变化的新增的依赖信息
            dest:   dst目的节点的依赖变化前的dst信息。  (dest+version)
        '''
        if dest in self.node_dp.get(X):
            # X依赖于 依赖变化前的 dst
            remove_element = []

            for i in new_dp:
                if i.startswith(X):
                    # 删除改变dst中的X节点信息
                    remove_element.append(i)
            # 移除包含X的依赖。（因为X 依赖于自己）
            for element in remove_element:
                new_dp.remove(element)

            # 获取X依赖中不包含new_dp依赖的结果res
            res = new_dp - set( self.node_dp.get(X))

            if res == set():
                # X依赖于 引起dst变化的所有依赖源。
                # 更新X的依赖为 最新版本的 dst
                dest_id = dest[:self.NODE_LEN]
                old_v = int(dest[self.NODE_LEN:])
                new_dest_info = dest_id + str(old_v+1)
                for index in range(len(self.node_dp.get(X))):
                    if self.node_dp.get(X)[index] == dest:
                        # 更新为 X 的 dst依赖为最新版本。
                        self.node_dp.get(X)[index] = new_dest_info
                        # 说明发生循环的情况
                        self.cycle += 1
                        return
        return






    def merge_dp(self, source, dest):
        '''用来更新dst目的节点的依赖信息。

        参数：
            source: src源节点的唯一标识
            dest: dst目的节点的唯一标识

        返回值:
            change_dp : 判定导致依赖发生改变的依赖节点集合
        '''
        
        # 获取输入依赖队列，
        # 始终保留最新的依赖信息，若有 A->B—>C的溯源链。  则C的依赖为 [A，B]  若只有一个依赖，保留最新的B.
        input_dp = self.node_dp.get(source).copy()

        # 输入依赖为 ：源节点的依赖节点信息Dp 加上源节点信息Src
        src_info = source+str(self.node_v.get(source))
        # 按照时间顺序排序的队列【Dp,Src(最新)】
        input_dp.append(src_info)
        
        
        # dst的原有依赖集合
        dst_dp = set(self.node_dp.get(dest))


        # 删除input_dp依赖中包含的dst依赖 (因为dst的依赖默认包含自己，不需要添加。)
        remove_element = []
        for element in input_dp:
            # element可能是node的某个版本 如 node1
            if element.startswith(dest):
                remove_element.append(element)

        for element in remove_element:
            input_dp.remove(element)

        remove_element = []
        # 清除源依赖与input_dst中重复的元素。
        for element in self.node_dp.get(dest):
            # 删除旧的重复的元素
            if element in input_dp:
                remove_element.append(element)

        for element in remove_element:
            self.node_dp.get(dest).remove(element)

        # 传入input_dp到 目的节点Dst
        # 从右端加入队列，超出的左边元素（最远的依赖）被清除
        self.node_dp.get(dest).extend(input_dp)

        # 查看新增的依赖。<如果依赖发生改变，一定有新的依赖出现>
        new_dp = set(self.node_dp.get(dest)) - dst_dp

        return new_dp

def test_fun(compact:EnDependencyRemain, a, b):
    res = compact.enhence_dependency_remain_compact(a,b)
    print(a,'-->',b,'\t结果',res)


def test():

    # e_gs_compact = CompleteSemantic()
    en_compact = EnDependencyRemain(1, 2, 'test', 10000)
    # re = e_gs_compact.complete_semantic_compact('d82bb2b636461e219ec97bebab7b3f8dea775ee2a1c5719135825a36cfd47540', '8d9acc2c9792dfae09441b01d823edeedf6bbc92113f20f38e88a4c03e13c093')
    # re = e_gs_compact.complete_semantic_compact('d82bb2b636461e219ec97bebab7b3f8dea775ee2a1c5719135825a36cfd47540', '8d9acc2c9792dfae09441b01d823edeedf6bbc92113f20f38e88a4c03e13c093')
    # re = e_gs_compact.complete_semantic_compact('d8', '8d')
    # re = e_gs_compact.complete_semantic_compact('d8', '8d')
    # print(re)    
    #
    print("节点数据流示意图：A-Q-B-Q-A-Q-B-Q-A")
    # 两个实体之间的循环
    test_fun(en_compact,'A','Q')
    test_fun(en_compact,'Q','B')
    test_fun(en_compact,'B','Q')
    test_fun(en_compact,'Q','A')
    test_fun(en_compact,'A','Q')
    test_fun(en_compact,'Q','B')
    test_fun(en_compact,'B','Q')
    test_fun(en_compact,'Q','A')
    test_fun(en_compact,'A','Q')
    test_fun(en_compact, 'Q', 'B')
    test_fun(en_compact, 'B', 'Q')
    test_fun(en_compact, 'Q', 'A')

    print("节点数据流示意图：A-B A-B B-C A-B B-C X-A A-B B-C")
    # 两个实体之间的循环
    test_fun(en_compact,'A','B')
    test_fun(en_compact,'A','B')
    test_fun(en_compact,'B','C')
    test_fun(en_compact,'A','B')
    test_fun(en_compact,'B','C')
    test_fun(en_compact,'X','A')
    test_fun(en_compact,'A','B')
    test_fun(en_compact,'B','C')

    # # e_gs_compact.enhence_semantic_compact('P', 'Q')
    # # e_gs_compact.enhence_semantic_compact('Q', 'A')
    # re = e_gs_compact.enhence_semantic_compact('A', 'B')
    # print(re)
    # re = e_gs_compact.enhence_semantic_compact('B', 'A')
    # print(re)
    # re = e_gs_compact.enhence_semantic_compact('B', 'A')
    # print(re)
    # re = e_gs_compact.enhence_semantic_compact('B', 'A')
    # print(re)
    #
    # e_gs_compact = CompleteSemantic()
    # print("节点数据流示意图：Am-Bm-Cm-Am-Bm-Cm-Am-Bm")
    # # 3个实体之间的循环
    # re = e_gs_compact.complete_semantic_compact('Am','Bm')
    # print(re)
    # re = e_gs_compact.complete_semantic_compact('Bm', 'Cm')
    # print(re)
    # re = e_gs_compact.complete_semantic_compact('Cm', 'Am')
    # print(re)
    # re = e_gs_compact.complete_semantic_compact('Am', 'Bm')
    # print(re)
    # re = e_gs_compact.complete_semantic_compact('Bm', 'Cm')
    # print(re)
    # re = e_gs_compact.complete_semantic_compact('Cm', 'Am')
    # print(re)
    # re = e_gs_compact.complete_semantic_compact('Am', 'Bm')
    # print(re)


if __name__ == '__main__':
    test()

